/**
 * Copyright (c) 2011-2016, James Zhan 詹波 (jfinal@126.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jfinal.aop;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jfinal.mvc.Action;
import com.jfinal.mvc.Controller;
import com.jfinal.mvc.render.ActionException;
import com.jfinal.mvc.render.RenderFactory;

import net.sf.cglib.proxy.MethodProxy;

/**
 * Invocation is used to invoke the interceptors and the target method
 */
public class Invocation {
    private static final Logger logger = LoggerFactory.getLogger(Invocation.class);

    private Action action;

    private boolean useInjectTarget;
    private Object target;
    private Method method;
    private Object[] args;
    private MethodProxy methodProxy;
    private Interceptor[] inters;

    private int index = 0;

    // InvocationWrapper need this constructor
    protected Invocation() {
        this.action = null;
    }

    public Invocation(Action action, Controller controller, Object[] args) {
        this.action = action;
        this.inters = action.getInterceptors();
        this.target = controller;
        this.method = action.getMethod();
        this.args = args;
    }

    public Invocation(Object target, boolean useInjectTarget, Method method, Object[] args, MethodProxy methodProxy,
                      Interceptor[] inters) {
        this.action = null;
        this.useInjectTarget = useInjectTarget;
        this.target = target;
        this.method = method;
        this.args = args;
        this.methodProxy = methodProxy;
        this.inters = inters;
    }

    public Object invoke() {
        if (index < inters.length) {
            return inters[index++].intercept(this);
        } else if (index++ == inters.length) { // index++ ensure invoke action only one time
            try {
                // Invoke the action
                if (action != null) {
                    return method.invoke(target, createParamBean(action, (Controller) target, args));
                }
                // Invoke the method
                else {
                    // if (!Modifier.isAbstract(method.getModifiers()))
                    // returnValue = methodProxy.invokeSuper(target, args);
                    if (useInjectTarget)
                        return methodProxy.invoke(target, args);
                    return methodProxy.invokeSuper(target, args);
                }
            } catch (InvocationTargetException e) {
                Throwable t = e.getTargetException();
                throw t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(e);
            } catch (RuntimeException e) {
                throw e;
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        }
        return null;
    }

    private Object[] createParamBean(Action action, Controller controller, Object[] args) {
        Class<?>[] actionMethodParameterTypes = action.getMethod().getParameterTypes();
        Object[] newargs = new Object[actionMethodParameterTypes.length];
        if (actionMethodParameterTypes.length == args.length + 1) {
            Class<?> paramType = actionMethodParameterTypes[newargs.length - 1];
            newargs[newargs.length - 1] = controller.getBean(paramType);
        } else if (actionMethodParameterTypes.length != args.length) {
            String message = String.format("参数个数不匹配，无法调用 Action方法！原始参数个数：%d，实际参数个数：%d",
                    actionMethodParameterTypes.length, args.length);
            logger.error(message);
            throw new ActionException(404, RenderFactory.me().getErrorRender(404));
        }
        for (int i = 0; i < args.length; i++) {// 转化类型
            try {
                newargs[i] = com.jfinal.mvc.TypeConverter.convert(actionMethodParameterTypes[i], (String) args[i]);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return newargs;
    }

    public Method getMethod() {
        return method;
    }

    public Object getTarget() {
        return target;
    }

    public String getActionKey() {
        return action.getActionKey();
    }

    public Controller getController() {
        return (Controller) target;
    }

    public String getControllerKey() {
        return action.getControllerKey();
    }

    public String getViewPath() {
        return action.getViewPath();
    }
}
